// server.c++
// server class implementation file

#include <iostream>
#include <SDL/SDL_net.h>
#include <cassert>
#include <vector>
#include <algorithm>

#include "server.h"
#include "map.h"
#include "objects.h"

extern Map* map;
extern Objects objects;

extern unsigned int phase;

void nextTurn();

Server::Server(const unsigned int port, const unsigned int maxSockets) {
	IPaddress ip;
	if (SDLNet_ResolveHost(&ip, NULL, port) == -1) {
		std::cout << "ERROR: SDLNet_ResolveHost: " << SDLNet_GetError()
		          << std::endl;
		exit(1);
	}
	serverSocket_ = SDLNet_TCP_Open(&ip);
	if (serverSocket_ == NULL) {
		std::cout << "ERROR: SDLNet_TCP_Open: " << SDLNet_GetError()
		          << std::endl;
		exit(1);
	}

	socketSet_ = SDLNet_AllocSocketSet(maxSockets);
	if (socketSet_ == NULL) {
		std::cout << "ERROR: SDLNet_AllocSocketSet: " << SDLNet_GetError()
		          << std::endl;
		exit(1);
	}
	maxSockets_ = maxSockets;

	// initialise message handlers
	messageHandlers_["MAPSEND"] 		= &Server::getMap;
	messageHandlers_["DISCONNECT"] 		= &Server::recvDisconnect;
	messageHandlers_["CHAT"] 			= &Server::recvChat;
	messageHandlers_["MOVE"] 			= &Server::recvMove;
}

Server::~Server() {
	for (int i = 0; i < (int)clients_.size(); i++) {
		sendDisconnect(i, "server is being shut down");
		removeClient(i);
	}
	clients_.clear();

	SDLNet_TCP_Close(serverSocket_);
	SDLNet_FreeSocketSet(socketSet_);
}

unsigned int Server::getNClients() {
	return clients_.size();
}

unsigned int Server::getNAliveClients() {
	unsigned int aliveClients = 0;
	for (unsigned int client = 0; client < clients_.size(); ++client) {
		if (clients_.at(client)->isConnected == true)
			++aliveClients;
	} 
	return aliveClients;
}

bool Server::addClient() {
	TCPsocket newClient = SDLNet_TCP_Accept(serverSocket_);
	if (newClient != NULL) {
		ClientData* client = new ClientData;
		client->socket = newClient;
	
		if (SDLNet_TCP_AddSocket(socketSet_, client->socket) == -1) {
			std::cout << "ERROR: SDLNet_TCP_AddSocket: " << SDLNet_GetError()
			          << std::endl;
			exit(1);
		}

		client->ip = *SDLNet_TCP_GetPeerAddress(newClient);
		client->buffer = NULL;
		client->messageLen = 0;
		client->isConnected = true;

		// Assign new client a name (later, clients will be able to set a name)
		client->name = new char[16];
		sprintf(client->name, "Player%u", (unsigned int)clients_.size());

		clients_.push_back(client);

		if (getNAliveClients() == maxSockets_) {
			sendDisconnect(clients_.size() - 1, "Server is full.");
			removeClient(clients_.size() - 1);
			std::cout<<"Disconnected new client: Server full.\n";
			return 0;
		}
 
		return 1;
	}
		return 0;
}

void Server::removeClient(const unsigned int clientNo) {
	if (clients_.at(clientNo)->isConnected == false)
		return;
	if (SDLNet_TCP_DelSocket(socketSet_, clients_.at(clientNo)->socket) == -1) {
		std::cout << "ERROR: SDLNet_DelSocket: " << SDLNet_GetError()
		          << std::endl;
		exit(1);
	}

	if (clients_.at(clientNo)->buffer != NULL)
		delete [] clients_.at(clientNo)->buffer;

	//delete [] clients_.at(clientNo)->name;

	unsigned int ip = clients_[clientNo]->ip.host;

	std::cout << "hgserver: disconnecting client: "
				<< ((ip >> 0) & 0xff) << "."
				<< ((ip >> 4) & 0xff) << "."
				<< ((ip >> 8) & 0xff) << "."
				<< ((ip >> 12) & 0xff) << ":"
				<< clients_.at(clientNo)->ip.port << std::endl;
	SDLNet_TCP_Close(clients_.at(clientNo)->socket);
	clients_.at(clientNo)->isConnected = false;
}

unsigned int Server::getClientIp(const unsigned int clientNo) {
	return clients_.at(clientNo)->ip.host;
}

unsigned int Server::getClientPort(const unsigned int clientNo) {
	return clients_.at(clientNo)->ip.port;
}

bool Server::getIsClientConnected(const unsigned int clientNo) {
	return clients_.at(clientNo)->isConnected;
}

const char* Server::getClientName(const unsigned int clientNo) {
	return clients_.at(clientNo)->name;
}

unsigned int Server::getNActiveSockets(const unsigned int timeout) {
	if (clients_.size() == 0) {
		return 0;
	}
	signed int nReady = SDLNet_CheckSockets(socketSet_, timeout);
	if (nReady == -1) {
		std::cout << "ERROR: SDLNet_CheckSockets: " << SDLNet_GetError()
		          << std::endl;
		exit(1);
	}
	return nReady;
}

unsigned int Server::receiveData(void* data, const unsigned int clientNo,
                                 const unsigned int maxLength) {
	assert(data != NULL);
	if (clients_.at(clientNo)->isConnected == false) {
		std::cout<<"WARNING: Attempt to receive data from disconnected client!\n";
		return -1;
	}
	if (SDLNet_SocketReady(clients_.at(clientNo)->socket) != 0) {
		signed int dataReceived = SDLNet_TCP_Recv(clients_.at(clientNo)->socket,
		                                          data, maxLength);
		if (dataReceived == -1) {
			std::cout << "ERROR: SDLNet_TCP_Recv: " << SDLNet_GetError()
			          << std::endl;
			removeClient(clientNo);
			sendDisconnect(clientNo, "connection error");
			return -1;
		} else if (dataReceived == 0) {
			removeClient(clientNo);
			sendDisconnect(clientNo, "connection error");
			return -1;
		} else {
			return dataReceived;
		} 
	} 
	return 0;
}

// Watch it, this returns -1 on fail.
unsigned int Server::sendData(const void* data, const unsigned int bytes, 
		unsigned int clientNo) {
	if (clients_.at(clientNo)->isConnected == false)
		return -1;
	unsigned int sent = SDLNet_TCP_Send(clients_.at(clientNo)->socket, 
			data, bytes);
	if (sent == bytes) {
		return sent;
	} else {
		removeClient(clientNo);
		sendDisconnect(clientNo, "connection error");
		return -1;
	}
}

// send the same data to all clients
void Server::sendAllData(const void* data, const unsigned int bytes) {
	for (int i = 0; i < (int)clients_.size(); i++) {
		if (clients_.at(i)->isConnected == true) {
			unsigned int sent = SDLNet_TCP_Send(clients_.at(i)->socket, 
					data, bytes);
			if (sent != bytes) {
				removeClient(i);
				sendDisconnect(i, "connection error");
			}
		}
	}
}

#define BUFFER_SIZE 256

// This should be a latency-proof method for receiving messages from clients.
void Server::getMessages() {
	unsigned int client; 
	unsigned int received;
	while (getNActiveSockets(0) != 0) {
		for (client = 0; client < getNClients(); ++client) {
			if (clients_.at(client)->isConnected == false)
				continue;
			if (clients_[client]->buffer == NULL) {
				clients_[client]->buffer = new char [BUFFER_SIZE];
			}
			for (;; ++clients_[client]->messageLen) {
				// Check to make sure the message won't overflow the buffer.
				if (clients_[client]->messageLen >= BUFFER_SIZE) {
					std::cout << "Message too long. Disconnecting client.\n";
					sendDisconnect(client, "message too long");
					removeClient(client);
					break;
				}
				received = receiveData(&((clients_[client]->buffer)
						[clients_[client]->messageLen]), client, 1);

				// If no data was received, break, the current buffer is kept
				// so that it can continue to be filled again later.
				if (received == 0) {
					break;
				}
				// Connection failed.. break.
				if (received == (unsigned int)-1) {
					break;
				}
				// Is it the end of the message?
				if ((clients_[client]->buffer)
						[clients_[client]->messageLen] == '\0') {
					std::cout << clients_[client]->buffer << "\n";
					parseMessage(clients_[client]->buffer, BUFFER_SIZE, client);
					// parseMessage() may disconnect the client!
					if (clients_[client]->isConnected == true) {
						delete [] clients_[client]->buffer;
						clients_[client]->buffer = NULL;
						clients_[client]->messageLen = 0;
					}
					break;
				}
			}
		}
	}
}

void Server::parseMessage(char* message, const unsigned int bufferSize,
                          const unsigned int sender) {
	// tokenise the message
	std::vector<std::string> tokens;
	std::string currentToken;
	unsigned int tokenStart = 0;
	for (unsigned int i = 0; ; ++i) {
		if (message[i] == '|') {
			currentToken.assign(&message[tokenStart], i - tokenStart);
			tokens.push_back(currentToken);
			tokenStart = (i + 1);
		} else if (message[i] == '\0') {
			currentToken.assign(&message[tokenStart]);
			tokens.push_back(currentToken);
			break;
		}
	}
	
	// if there is no message handler, report invalid message
	if (messageHandlers_.count(tokens.at(0)) == 0) {
		invalidMessage(message, "No message handler", sender);
		return;
	}
	
	// this calls the relevant message handler member.
	// sorry about the syntax
	(this->*messageHandlers_[tokens.at(0)])(tokens, message, sender);
}

bool Server::sendMessage(const char* message, const unsigned int client) {
	std::cout << "Sending:" << message << "\n";
	if (sendData(message, strlen(message) + 1, client) <= 0) {
		std::cout << "Map message sending to " << client << " failed.\n";
		return false;
	}
	return true;
}

void Server::sendAllMessage(const char* message) {
	std::cout << "Sending to all:" << message << "\n";
	sendAllData(message, strlen(message) + 1);
}

void Server::invalidMessage(const char* message, const char* reason, 
                            const unsigned int sender) {
	std::cout << "Invalid message: " << message << "\n"
	          << reason << "\nsent by: " << sender <<"\n";
	sendDisconnect(sender, reason);
	removeClient(sender);
}

// Message handlers ============================================================
void Server::getMap(std::vector<std::string>& tokens, const char* message,
                    const unsigned int sender) {
	
}

void Server::recvDisconnect(std::vector<std::string>& tokens,
                            const char* message, const unsigned int sender) {
	// Check for correct number of parameters
	if (tokens.size() != 2) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	sendDisconnect(sender, "client disconnected");
	std::cout << "Client[" << sender << "] has disconnected: " << tokens[1]
	          << "\n";
	removeClient(sender);
}

void Server::recvChat(std::vector<std::string>& tokens, const char* message,
                      const unsigned int sender) {
	if (tokens.size() != 2) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	if (tokens.at(1).size() > 255) {
		invalidMessage(message, "Chat too long", sender);
	}
	sendChat(sender, tokens.at(1).c_str());
}

void Server::recvMove(std::vector<std::string>& tokens, const char* message,
						 const unsigned int sender) {
	if (tokens.size() != 3) {
		invalidMessage(message, "Invalid parameter count", sender);
		return;
	}
	unsigned int wizard = 0;
	//unsigned int client = atoi(tokens.at(1).c_str());
	for (unsigned int i = 0; i < objects.getNWizards(); ++i) {
		if (objects.getWizard(i)->getClientId() == sender)
			wizard = i;
	}
	if (phase != PHASE_MOVEMENT) {
		invalidMessage(message, "not movement phase", sender);
		return;
	}
	if (!objects.getWizard(wizard)->getIsMyTurn()) {
		invalidMessage(message, "not your turn", sender);
		return;
	}


	unsigned int x = atoi(tokens.at(1).c_str());
	unsigned int y = atoi(tokens.at(2).c_str());

	if ((x < 0) || (x >= map->getWidth()) 
			|| (y < 0) || (y >= map->getHeight())) {
		invalidMessage(message, "cannot move outside of map", sender);
		return;
	}
	// FIXME: MORE MOVEMENT CHECKING CODE HERE (TO SEE IF ITS POSSIBLE)!!!!

	objects.getWizard(wizard)->setX(x);
	objects.getWizard(wizard)->setY(y);

	// relay this to the other clients
	sendMove(wizard, x, y);
	nextTurn();
}

// Message senders =============================================================
void Server::sendMap(const unsigned int client) { 
	unsigned int bytesSent = 0; 
	unsigned int width = map->getWidth(); 
	unsigned int height = map->getHeight(); 
	const unsigned int totalBytes = width * height;
	unsigned char byte;
	char message[64];
	snprintf(message, 64, "MAPSEND|%u|%u", width, height);
	sendMessage(message, client);
	std::cout << "Sending map. Dimensions: " << width << " x " << height
	          << "\n";
	while (bytesSent < totalBytes) {
		byte = map->getTile(0, bytesSent % width, bytesSent / width);
		if (sendData(&byte, 1, client) <= 0) {
			std::cout << "Map sending to " << client << " failed. \n";
			return;
		}
		++bytesSent;
		if (bytesSent % (totalBytes / 100) == 1) {
			std::cout << "\r" << bytesSent << " bytes sent. ["
			          << (bytesSent / totalBytes) * 100 << "%]";
		}
	}
	std::cout << "\r" << bytesSent << " bytes sent. [" 
	          << (bytesSent / totalBytes) * 100 << "%]\n";
	std::cout << "Map has been fully sent.\n";
}

void Server::sendAddFountain(const unsigned int client, const unsigned int x,
							 const unsigned int y) {
	char message[64];
	snprintf(message, 64, "ADDFOUNTAIN|%u|%u", x, y);
	sendMessage(message, client);
}

void Server::sendDisconnect(const unsigned int client, const char* reason) {
	if (strlen(reason) > 64) {
		std::cout << "Error: Disconnect reason too long\n";
		return;
	}
	char message[128];
	snprintf(message, 128, "DISCONNECT|%u|%s", client, reason);
	sendAllMessage(message);
}

void Server::sendChat(const unsigned int client, const char* chat) {
	if (strlen(chat) > 256) {
		std::cout << "Error: Chat too long\n";
		return;
	}
	char message[512];
	snprintf(message, 512, "CHAT|%u|%s", client, chat);
	sendAllMessage(message);
}

void Server::sendNewWizard(const unsigned int client, const unsigned int x,
						   const unsigned int y, const unsigned int clientId) {
	char message[128];
	snprintf(message, 128, "NEWWIZARD|%u|%u|%u", x, y, clientId);
	sendMessage(message, client);
}

void Server::sendNewClientInfo(const unsigned int client, 
							   const unsigned int clientId, 
                       		   const bool isConnected, const char* name) {
	char message[128];
	if (strlen(name) > 64) {
		std::cout << "Error: Name too long\n";
		return;
	}
	snprintf(message, 128, "NEWCLIENTINFO|%u|%u|%s", clientId,
	         (unsigned int)isConnected, name);
	sendMessage(message, client);
}

void Server::sendYou(unsigned int client) {
	char message[128];
	snprintf(message, 128, "YOU|%u", client);
	sendMessage(message, client);
}

void Server::sendNewTurn(const unsigned int wizard) {
	char message[128];
	snprintf(message, 128, "NEWTURN|%u", wizard);
	sendAllMessage(message);
}

void Server::sendNewPhase(const unsigned int phase) {
	char message[128];
	snprintf(message, 128, "NEWPHASE|%u", phase);
	sendAllMessage(message);
}

void Server::sendNewRound() {
	char message[128];
	snprintf(message, 128, "NEWROUND");
	sendAllMessage(message);
}

void Server::sendMove(const unsigned int clientId, const unsigned int x, 
				  const unsigned int y) {
	char message[128];
	snprintf(message, 128, "MOVE|%u|%u|%u", clientId, x, y);
	sendAllMessage(message);
}

